import importlib.util
import pkgutil
from pathlib import Path
from typing import List, Set

from axiom_boot.logging.setup import get_logger

logger = get_logger(__name__)


class ComponentScanner:
    """
    组件扫描器。

    负责根据给定的基础目录，发现所有有效的 Python 模块。
    它的逻辑基于 `pkgutil.walk_packages`，可以可靠地处理命名空间包和常规包。
    """

    def __init__(self, base_packages: List[str]):
        """
        初始化扫描器。

        Args:
            base_packages: 一个包含基础包名称的列表 (例如, ['src', 'your_app'])。
                           扫描器将会在这些 Python 包中寻找模块。
        """
        self._base_packages = base_packages

    def scan(self) -> Set[str]:
        """
        执行扫描操作，包含框架内部扫描和用户项目扫描两个阶段。

        Returns:
            一个包含所有发现的模块名称的集合。
        """
        discovered_modules: Set[str] = set()

        # 1. 动态地、完整地扫描整个 axiom_boot 框架包
        self._scan_framework_package(discovered_modules)

        # 2. 扫描用户配置的包
        self._scan_user_packages(discovered_modules)

        logger.info(f"总扫描完成，共发现 {len(discovered_modules)} 个唯一的模块。")
        return discovered_modules

    def _scan_framework_package(self, modules: Set[str]):
        """
        动态扫描整个 axiom_boot 包，以自动发现所有框架内置模块。
        此版本使用标准的、基于导入的扫描方式，而不是基于文件路径的猜测，因此更加健壮。
        """
        try:
            # 直接导入框架的主包
            import axiom_boot
            
            logger.debug(f"开始动态扫描框架包，路径: {axiom_boot.__path__}")

            # 使用包的 __path__ 和 __name__ 来进行标准的、可靠的递归扫描
            prefix = axiom_boot.__name__ + '.'
            for module_info in pkgutil.walk_packages(path=axiom_boot.__path__, prefix=prefix):
                modules.add(module_info.name)
            
            # 确保主包自身也被包含
            modules.add(axiom_boot.__name__)

            logger.debug(f"框架内部扫描完成，发现了 {len(modules)} 个模块。")
        except Exception as e:
            logger.error(f"动态扫描框架 'axiom_boot' 时发生严重错误: {e}", exc_info=True)


    def _scan_user_packages(self, modules: Set[str]):
        """
        扫描用户在配置中指定的 Python 包。
        这种方法是健壮的，不受当前工作目录 (CWD) 的影响。
        """
        if not self._base_packages:
            logger.debug("没有配置任何用户扫描包，跳过。")
            return

        logger.debug(f"开始扫描用户组件，基础包: {self._base_packages}")

        for package_name in self._base_packages:
            try:
                spec = importlib.util.find_spec(package_name)
                if spec is None:
                    logger.warning(f"无法找到名为 '{package_name}' 的包，已跳过扫描。请确保它已正确安装或在 PYTHONPATH 中。")
                    continue

                if not spec.has_location:
                    logger.warning(f"包 '{package_name}' 没有物理位置 (has_location=False)，无法扫描。")
                    continue
                
                # 对于常规包，submodule_search_locations 是一个包含包目录的列表
                search_paths = spec.submodule_search_locations
                if search_paths is None:
                    # 如果是单个模块文件，origin 会指向该文件
                    if spec.origin:
                        search_paths = [str(Path(spec.origin).parent)]
                    else:
                        logger.warning(f"无法确定包 '{package_name}' 的搜索路径。")
                        continue

                prefix = package_name + '.'
                logger.debug(f"正在扫描包 '{package_name}'，路径: {search_paths}，前缀: '{prefix}'")

                for module_info in pkgutil.walk_packages(path=search_paths, prefix=prefix):
                    # 确保框架模块不会被重复添加
                    if module_info.name.startswith("axiom_boot."):
                        continue
                    modules.add(module_info.name)

            except Exception as e:
                logger.error(f"扫描用户包 '{package_name}' 时发生未知错误: {e}", exc_info=True)